﻿using UnityEngine;
using System;
using System.Collections.Generic;
using FM_Mono;
using UnityEngine.U2D;
using Debug = UnityEngine.Debug;
using UObject = UnityEngine.Object;
using FibMatrix.Schedule;
using UnityEngine.Profiling;

namespace FibMatrix
{
    public class ResourceFacade
    {
        #region Fields

        private static ResourceFacade _instance = new ResourceFacade();
        private static List<UObject> _autoUnloadList = new List<UObject>();

        private AbstractResourceFacadeImpl _impl;
        private string _prefabPathPrefix;
        private Dictionary<string, string> _idConvertCacheMap = new Dictionary<string, string>(200);

        private Dictionary<Type, ResourceType> _typeResTypeMap = new Dictionary<Type, ResourceType>()
        {
            {typeof(GameObject), ResourceType.Prefab},
            {typeof(Material), ResourceType.Material},
            {typeof(Shader), ResourceType.Shader},
            {typeof(TextAsset), ResourceType.TextAsset},
            {typeof(Texture), ResourceType.Texture},
            {typeof(SpriteAtlas), ResourceType.SpriteAtlas},
            {typeof(RuntimeAnimatorController), ResourceType.AnimatorController},
            {typeof(AnimatorOverrideController), ResourceType.AnimatorOverrideController},
            {typeof(AnimationClip), ResourceType.AnimationClip},
        };
        
        private int _checkReleaseHandler;

        #endregion

        #region Public Static Methods

        [AcquireOnce]
        public static ResourceFacade instance
        {
            get
            {
                return _instance;
            }
        }

        public static void AddAutoUnloadObject(UObject go)
        {
            if (go)
            {
                _autoUnloadList.Add(go);
            }
        }

        public static void RemoveAutoUnloadObject(UObject go)
        {
            if (go)
            {
                _autoUnloadList.Remove(go);
            }
        }

        public static void ClearAutoUnloadObjects()
        {
            int count = _autoUnloadList.Count;

            for (int i = 0; i < count; ++i)
            {
                UObject autoUnload = _autoUnloadList[i];
                if (autoUnload)
                {
                    _instance.Unload(autoUnload);
                }
            }

            _autoUnloadList.Clear();
        }

        public static void GC(bool clearUnHandleResources = true)
        {
            Profiler.BeginSample("ResFacade.GC");
            AbstractResourceFacadeImpl impl = instance._impl;
            if (impl != null)
            {
                impl.RefObjectCheckInGC();
#if UNITY_EDITOR
                // impl.ReportRefObjectCheck();
                // impl.ReportNonhintRefObjectCheck();
#endif
                if (clearUnHandleResources)
                {
                    impl.ClearUnhandleResources();
                }
                impl.CheckReourceRelease(true, true);
            }
            else
            {
                Debug.LogWarning("Invoke \"GC\" on uninitialized ResourceFacade");
            }
            Profiler.EndSample();
            System.GC.Collect();
        }

        public static GameObject Instantiate(GameObject original)
        {
            return _instance.InstantiateImpl(original) as GameObject;
        }
        
        public static UObject Instantiate(UObject original)
        {
            return _instance.InstantiateImpl(original);
        }
        
        public static GameObject NewGameObject(string name = "")
        {
            return _instance.NewGameObjectImpl(name);
        }
        
        public static GameObject NewGameObjectWithoutRef(string name = "")
        {
            return new GameObject(name);
        }

        public static void Destroy(UnityEngine.Object obj)
        {
            _instance.DestroyImpl(obj);
        }

        //public static void Destroy(UObject obj, float t)
        //{
        //       _instance.DestroyImpl(obj, t);
        //}
        #endregion


        #region Public Methods

        /// <summary>
        /// 初始化实际使用的资源模式
        /// </summary>
        /// <param name="initConfig"></param>
        public void Initialize(IResourceInitConfig initConfig)
        {
            if (_impl != null)
            {
                _impl.Dispose();
                _impl = null;
            }
            AssetActionLogger.Init();
            this._prefabPathPrefix = initConfig.prefabPathPrefix;

            switch (initConfig.resourceMode)
            {
                case ResourceMode.AssetBundle:
                    this._impl = new BundleResourceFacadeImpl();
                    break;
#if UNITY_EDITOR                
                case ResourceMode.AssetDatabase:
                    this._impl = new AssetDBResourceFacadeImpl();
                    break;
#endif
                case ResourceMode.Default:
                default:
                    this._impl = new DefaultResourceFacadeImpl();
                    break;
            }
            this._impl.Initialize(initConfig);
            this.ClearReleaseCheckHandler();
            this._checkReleaseHandler = GlobalScheduler.AddTimeLimitUpdator(initConfig.releaseCheckInterval, this.PeriodReleaseCheck);
        }

        public void Dispose()
        {
            _shaderCache.Clear();
            this.ClearReleaseCheckHandler();
            if (this._impl != null)
            {
                this._impl.Dispose();
                this._impl = null;
            }
            AssetActionLogger.Dispose();
        }
        
        public bool HasObject(string localPath, ResourceType type, string overrideExtension, string prefix)
        {
            DetermineLoadParams(type,  overrideExtension, out var extension, out _);
            string id = GetPathID(prefix, localPath, extension, true);
            return _impl.HasObject(id);
        }

        public bool HasObject(string localPath, ResourceType type, string prefix = "")
        {
            return HasObject(localPath, type, null, prefix);
        }

        public bool HasPrefab(string localPath)
        {
            if (string.IsNullOrEmpty(localPath))
            {
                return false;
            }
            return HasObject(localPath, ResourceType.Prefab, _prefabPathPrefix);
        }

        /// <summary>
        /// 资源是否可以同步加载，需要下载就不能同步加载
        /// </summary>
        /// <param name="assetPath"></param>
        /// <returns></returns>
        public bool CanUseLoadSync(string localPath, ResourceType type, string overrideExtension = null, string prefix = "")
        {
            DetermineLoadParams(type,  overrideExtension, out var extension, out _);
            string id = GetPathID(prefix, localPath, extension, true);
            return _impl.CanUseLoadSync(id);
        }

        void LogSyncLoad(string assetPath, string msgPrefix = null)
        {
            if (Application.isEditor == false)
                return;
            var devCfgInst = ResourceDevCfg.Instance;
            if (devCfgInst.logSyncLoadAsWarning || devCfgInst.logSyncLoadAsError)
            {
                string str = null;
                if (string.IsNullOrEmpty(msgPrefix))
                    msgPrefix = "LoadObject NotInAsync";
                str =$"{msgPrefix}: {assetPath}";
                
                if (devCfgInst.logSyncLoadAsWarning)
                    Debug.LogWarning(str);
                if (devCfgInst.logSyncLoadAsError)
                    Debug.LogError(str);
            }
        }

#if !DISABLE_SYNC_LOAD
        public UObject LoadObject(string localPath, string overrideExtension, Type type, string prefix = "")
        {
            if (!_typeResTypeMap.TryGetValue(type, out var resourceType))
            {
                resourceType = ResourceType.Other;
            }

            return LoadObject(localPath, overrideExtension, resourceType, prefix);
        }

        public UObject LoadObject(string localPath, string overrideExtension, ResourceType type = ResourceType.Other, string prefix = "")
        {
            if (string.IsNullOrEmpty(localPath))
            {
                return null;
            }

            string extension;
            bool create;
            DetermineLoadParams(type, overrideExtension, out extension, out create);
            string id = GetPathID(prefix, localPath, extension, true);
            LogSyncLoad(id);
            var res = _impl.LoadObject(id, create);
            if (res == null)
                Debug.LogError($"LoadObject failed:{id}");
            return res;
        }
        
        public UObject LoadObject(string localPath, ResourceType type = ResourceType.Other, string prefix = "")
        {
            return LoadObject(localPath, null, type, prefix);
        }

        public GameObject LoadPrefab(string localPath)
        {
            return LoadObject(localPath, ResourceType.Prefab, _prefabPathPrefix) as GameObject;
        }

        //public GameObject LoadPrefabWithoutInstantiate(string localPath)
        //{
        //	string extension;
        //	bool create;
        //	DetermineLoadParams(ResourceType.Prefab, out extension, out create);
        //	string id = GetPathID(_prefabPathPrefix, localPath, extension, true);
        //	return _impl.LoadObject(id, false, false) as GameObject;
        //}


        public Component LoadComponent(string localPath, Type componentType)
        {
            GameObject prefab = LoadPrefab(localPath);
            Component cpnt = null;
            if (prefab != null)
            {
                cpnt = prefab.GetComponent(componentType);
            }
            if (cpnt == null)
                Debug.LogError($"LoadComponent failed:{localPath}, {componentType.ToString()}");
            return cpnt;
        }

        //TODO:加一个type？
        public LightProbes LoadLightProbes(string localPath)
        {
            return LoadObject(localPath, ResourceType.Other, "") as LightProbes;
        }

        public Texture2D LoadTexture2D(string localPath)
        {
            return LoadObject(localPath, ResourceType.Texture, "") as Texture2D;
        }

        public TextAsset LoadTextAsset(string localPath, string prefix = "")
        {
            return LoadObject(localPath, ResourceType.TextAsset, prefix) as TextAsset;
        }

        public TextAsset LoadBytesTextAsset(string localPath, string prefix = "")
        {
            return LoadObject(localPath, ".bytes", ResourceType.TextAsset, prefix) as TextAsset;
        }

        public Material LoadMaterial(string localPath)
        {
            return LoadObject(localPath, ResourceType.Material, "") as Material;
        }

        public SpriteAtlas LoadSpriteAtlas(string localPath)
        {
            return LoadObject(localPath, ResourceType.SpriteAtlas, "") as SpriteAtlas;
        }
        
        public T LoadOtherAsset<T>(string localPath) where T : UObject
        {
            return LoadObject(localPath,null, typeof(T), "") as T;
        }
#endif
        
        
        public IAsyncResourceRequest LoadObjectAsync(string localPath, Type type, string prefix = "")
        {
            return LoadObjectAsync(localPath, type, prefix, 0);
        }

        /// <summary>
        /// 即使加载失败，仍然对外执行回调
        /// </summary>
        /// <param name="localPath"></param>
        /// <param name="overrideExtension"></param>
        /// <param name="type"></param>
        /// <param name="prefix"></param>
        /// <param name="priority"></param>
        /// <returns>回调执行后，request会被dispose，不可再使用</returns>
        public IAsyncResourceRequest LoadObjectAsync(string localPath, string overrideExtension, Type type,
            string prefix, int priority)
        {
            var sd = AssetActionLogger.GetActionStartData();
            if (!_typeResTypeMap.TryGetValue(type, out var resType))
            {
                resType = ResourceType.Other;
            }
            string extension;
            bool create;
            DetermineLoadParams(resType, overrideExtension, out extension, out create);
            string id = GetPathID(prefix, localPath, extension, true);
            //	    Profiler.BeginSample("LoadObjectAsync");
            var request = _impl.LoadObjectAsync(id, type, create, priority);

            AssetActionLogger.RecordAction(sd, id, AssetBundleActionType.LoadAssetAsync);
            //	    Profiler.EndSample();
            return request;
        }
        
        public IAsyncResourceRequest LoadObjectAsync(string localPath, Type type, string prefix,int priority)
        {
            return LoadObjectAsync(localPath, null, type, prefix, priority);
        }

        public IAsyncResourceRequest LoadPrefabAsync(string localPath)
        {
            //        CustomLog("LoadPrefabAsync : " + localPath + " unRefMode : " + unRefMode);
            return LoadPrefabAsync(localPath, 0);
        }
        public IAsyncResourceRequest LoadPrefabAsync(string localPath, int priority)
        {
            //        CustomLog("LoadPrefabAsync : " + localPath + " unRefMode : " + unRefMode);
            return LoadObjectAsync(localPath, typeof(GameObject), _prefabPathPrefix, priority);
        }
        
        
        
        public void Unload(UObject obj)
        {
            DestroyImpl(obj);
        }
        public void Unload(GameObject gameObject)
        {
            DestroyImpl(gameObject);
        }
        public void Unload(Texture2D texture2D)
        {
            DestroyImpl(texture2D);
        }
        public void Unload(TextAsset textAsset)
        {
            DestroyImpl(textAsset);
        }
        public void Unload(Material material)
        {
            DestroyImpl(material);
        }

        
        
        Dictionary<string,Shader> _shaderCache = new Dictionary<string, Shader>();
#if !DISABLE_SYNC_LOAD
        public Shader[] GetAllShaders(string shaderFileName)
        {
            DetermineLoadParams(ResourceType.Other, null, out var extension, out _);
            var sd = AssetActionLogger.GetActionStartData();
            string id = GetPathID("", shaderFileName, extension, false);
            LogSyncLoad(id, "GetAllShaders NotInAsync");
            Shader[] shaders = _impl.LoadAllAssetOnce<Shader>(id);
            AssetActionLogger.RecordAction(sd, id, AssetBundleActionType.LoadAsset);
            if (shaders == null)
                Debug.LogError($"GetAllShaders failed:{shaderFileName}");
            
            _shaderCache.Clear();
            int count = shaders?.Length ?? 0;
            for (int i = 0; i < count; i++)
            {
                var shader = shaders[i];
                _shaderCache.Add(shader.name, shader);
            }
            return shaders;
        }

        public Shader GetShader(string name)
        {
            if (_shaderCache.Count == 0)
            {
                //UNITY_EDITOR
                return Shader.Find(name);
            }
            
            Shader result = null;
            _shaderCache.TryGetValue(name, out result);
            if (result == null)
                Debug.LogError($"GetShader failed:{name}");
            return result;
        }

        public ShaderVariantCollection[] GetAllShaderVariantCollection(string shaderFileName)
        {
            DetermineLoadParams(ResourceType.Other, null, out var extension, out _);
            var sd = AssetActionLogger.GetActionStartData();
            string id = GetPathID("", shaderFileName, extension, false);
            LogSyncLoad(id, "GetAllSVC NotInAsync");
            var res = _impl.LoadAllAssetOnce<ShaderVariantCollection>(id);
            AssetActionLogger.RecordAction(sd, id, AssetBundleActionType.LoadAsset);
            if (res == null)
                Debug.LogError($"GetAllSVC failed:{shaderFileName}");
            return res;
        }
#endif

        #endregion

        #region Private Methods

        /// <summary>
        /// 基于实例化对象再次实例化，需要找到源资源处理计数
        /// </summary>
        /// <param name="original">已经实例化的对象</param>
        /// <returns></returns>
        private UObject InstantiateImpl(UObject original)
        {
            if (_impl == null)
            {
                Debug.LogError("Invoke \"Instantiate\" on uninitialized ResourceFacade");
                return null;
            }
            int instanceID;
            AbstractLoadItem hint;
            UObject res = null;
            if (original is Component)
            {
                Type type = original.GetType();
                original = (original as Component).gameObject;
                instanceID = original.GetInstanceID();
                hint = _impl.AcquireLoadItemByInstanceId(instanceID);
                res = (_impl.Instantiate(original, hint) as GameObject).GetComponent(type);
            }
            else
            {
                instanceID = original.GetInstanceID();
                hint = _impl.AcquireLoadItemByInstanceId(instanceID);
                res = _impl.Instantiate(original, hint);
                
            }
            if (res == null)
                Debug.LogError($"InstantiateImpl failed:{original?.name}");

            return res;
        }

        private GameObject NewGameObjectImpl(string name = "")
        {
            if (_impl == null)
            {
                Debug.LogWarning("Invoke \"Create GameObject\" on uninitialized ResourceFacade");
                return null;
            }
            
            return _impl.NewGameObject(name);
        }

        private void DestroyImpl(UObject obj, float delayTime = 0)
        {
            if (_impl == null)
            {
#if UNITY_EDITOR
                UObject.Destroy(obj);
#endif
                Debug.LogWarning("Invoke \"Destroy\" on uninitialized ResourceFacade");
                return;
            }
            _impl.Destroy(obj, true, delayTime);
        }

        private string GetPathID(string prefix, string localPath, string extension, bool useCache)
        {
            string result;
            if (!useCache || !_idConvertCacheMap.TryGetValue(localPath, out result))
            {
                result = _impl.GenerateLoadURL(prefix, localPath, extension);
                if (useCache)
                {
                    _idConvertCacheMap.Add(localPath, result);
                }
            }
            return result;
        }

        private void DetermineLoadParams(ResourceType type, string overrideExtension, out string extension, out bool instantiated)
        {
            instantiated = false;
            switch (type)
            {
                case ResourceType.Prefab:
                    extension = ".prefab";
                    instantiated = true;
                    break;
                case ResourceType.Material:
                    extension = ".mat";
                    break;
                case ResourceType.Shader:
                    extension = ".shader";
                    break;
                case ResourceType.Texture:
                    extension = ".tex";
                    break;
                case ResourceType.TextAsset:
                    extension = ".txt";
                    break;                
                case ResourceType.SpriteAtlas:
                    extension = ".spriteatlas";
                    break;
                case ResourceType.AnimatorOverrideController:
                    extension = ".overrideController";
                    break;
                default:
                    extension = ".asset";
                    break;
            }

            extension = overrideExtension ?? extension;
        }

        private void PeriodReleaseCheck(object context)
        {
            _impl.RefObjectPeriodicCheck();
            _impl.CheckReourceRelease();
        }

        private void ClearReleaseCheckHandler()
        {
            GlobalScheduler.RemoveLimitUpdator(ref this._checkReleaseHandler);
        }

        #endregion

        [IgnoreGen]
        public static void InspectorGUI()
        {
            instance?._impl?.InspectorGUI();
        }
    }
}
